/*******************************************************************************
 * Copyright (c) 2013 University of Stuttgart.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and the Apache License 2.0 which both accompany this distribution,
 * and are available at http://www.eclipse.org/legal/epl-v10.html
 * and http://www.apache.org/licenses/LICENSE-2.0
 *
 * Contributors:
 *     Oliver Kopp - initial API and implementation
 *******************************************************************************/
/*
 * Modifications Copyright 2016 ZTE Corporation.
 */

package org.eclipse.winery.common;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.Map.Entry;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.winery.common.constants.Namespaces;
import org.eclipse.winery.common.constants.QNames;
import org.eclipse.winery.common.propertydefinitionkv.PropertyDefinitionKV;
import org.eclipse.winery.common.propertydefinitionkv.PropertyDefinitionKVList;
import org.eclipse.winery.common.propertydefinitionkv.WinerysPropertiesDefinition;
import org.eclipse.winery.model.tosca.TBoundaryDefinitions;
import org.eclipse.winery.model.tosca.TCapability;
import org.eclipse.winery.model.tosca.TCapabilityDefinition;
import org.eclipse.winery.model.tosca.TEntityTemplate;
import org.eclipse.winery.model.tosca.TEntityType;
import org.eclipse.winery.model.tosca.TExtensibleElements;
import org.eclipse.winery.model.tosca.TNodeTemplate;
import org.eclipse.winery.model.tosca.TNodeTemplate.Capabilities;
import org.eclipse.winery.model.tosca.TNodeTemplate.Requirements;
import org.eclipse.winery.model.tosca.TNodeType;
import org.eclipse.winery.model.tosca.TPlan;
import org.eclipse.winery.model.tosca.TPlans;
import org.eclipse.winery.model.tosca.TRelationshipTemplate;
import org.eclipse.winery.model.tosca.TRelationshipTemplate.SourceElement;
import org.eclipse.winery.model.tosca.TRelationshipTemplate.TargetElement;
import org.eclipse.winery.model.tosca.TRelationshipType;
import org.eclipse.winery.model.tosca.TRequirement;
import org.eclipse.winery.model.tosca.TRequirementDefinition;
import org.eclipse.winery.model.tosca.TServiceTemplate;
import org.eclipse.winery.model.tosca.TTopologyTemplate;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

public class ModelUtilities {

    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(ModelUtilities.class);


    /**
     * This is a special method for Winery. Winery allows to define a property definition by
     * specifying name/type values. Instead of parsing the extensible elements returned
     * TDefinitions, this method is a convenience method to access this information
     * 
     * @param t the entitytype to read the properties definition from
     * @return a WinerysPropertiesDefinition object, which includes a map of name/type-pairs
     *         denoting the associated property definitions. A default element name and namespace is
     *         added if it is not defined in the underlying XML. null if no Winery specific KV
     *         properties are defined for the given entity type
     */
    public static WinerysPropertiesDefinition getWinerysPropertiesDefinition(TEntityType et) {
        // similar implementation as
        // org.eclipse.winery.repository.resources.entitytypes.properties.PropertiesDefinitionResource.getListFromEntityType(TEntityType)
        WinerysPropertiesDefinition res = null;
        for (Object o : et.getAny()) {
            if (o instanceof WinerysPropertiesDefinition) {
                res = (WinerysPropertiesDefinition) o;
                if ("Properties".equals(res.getElementName())) {
                    return res;
                }
            }
        }

        if (res != null) {
            // we put defaults if elementname and namespace have not been set

            if (res.getElementName() == null) {
                res.setElementName("Properties");
            }

            if (res.getNamespace() == null) {
                // we use the targetnamespace of the original element
                String ns = et.getTargetNamespace();
                if (!ns.endsWith("/")) {
                    ns += "/";
                }
                ns += "propertiesdefinition/winery";
                res.setNamespace(ns);
            }
        }

        return res;
    }
    
    public static List<WinerysPropertiesDefinition> getWinerysPropertiesDefinitions(TEntityType et){
        List<WinerysPropertiesDefinition> wpds = new ArrayList<WinerysPropertiesDefinition>();
        for(Object o : et.getAny()){
            if(o instanceof WinerysPropertiesDefinition)
                wpds.add((WinerysPropertiesDefinition) o);
        }
        return wpds;
    }

    /**
     * This is a special method for Winery. Winery allows to define a property by specifying
     * name/value values. Instead of parsing the XML contained in TNodeType, this method is a
     * convenience method to access this information
     * 
     * The return type "Properties" is used because of the key/value properties.
     * 
     * @param template the node template to get the associated properties
     */
    public static Properties getPropertiesKV(TEntityTemplate template) {
        Properties properties = new Properties();
        org.eclipse.winery.model.tosca.TEntityTemplate.Properties tprops = template.getProperties();
        if (tprops != null) {
            // no checking for validity, just reading
            Element el = (Element) tprops.getAny();
            if (el == null) {
                // somehow invalid .tosca. We return empty properties instead of throwing a NPE
                return properties;
            }
            NodeList childNodes = el.getChildNodes();
            for (int i = 0; i < childNodes.getLength(); i++) {
                Node item = childNodes.item(i);
                if (item instanceof Element) {
                    String key = item.getLocalName();
                    String value = item.getTextContent();
                    properties.put(key, value);
                }
            }
        }
        return properties;
    }

    /**
     * This is a special method for Winery. Winery allows to define a property by specifying
     * name/value values. We convert the given Properties to XML.
     * 
     * @param wpd the Winery's properties definition of the type of the given template (i.e., wpd =
     *        getWinerysPropertiesDefinition(template.getType()))
     * @param template the node template to set the associated properties
     */
    public static void setPropertiesKV(WinerysPropertiesDefinition wpd, TEntityTemplate template,
            Properties properties) {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db;
        try {
            db = dbf.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            ModelUtilities.logger.debug(e.getMessage(), e);
            throw new IllegalStateException("Could not instantiate document builder", e);
        }
        Document doc = db.newDocument();

        Element root = doc.createElementNS(wpd.getNamespace(), wpd.getElementName());
        doc.appendChild(root);

        // we produce the serialization in the same order the XSD would be generated (because of the
        // usage of xsd:sequence)
        for (PropertyDefinitionKV prop : wpd.getPropertyDefinitionKVList()) {
            // we always write the element tag as the XSD forces that
            Element element = doc.createElementNS(wpd.getNamespace(), prop.getKey());
            root.appendChild(element);
            String value = properties.getProperty(prop.getKey());
            if (value != null) {
                Text text = doc.createTextNode(value);
                element.appendChild(text);
            }
        }

        org.eclipse.winery.model.tosca.TEntityTemplate.Properties tprops =
                new org.eclipse.winery.model.tosca.TEntityTemplate.Properties();
        tprops.setAny(doc.getDocumentElement());
        template.setProperties(tprops);
    }

    /**
     * Generates a XSD when Winery's K/V properties are used. This method is put here instead of
     * WinerysPropertiesDefinitionResource to avoid generating the subresource
     * 
     * public because of the usage by TOSCAEXportUtil
     * 
     * @return empty Document, if Winery's Properties Definition is not fully filled (e.g., no
     *         wrapping element defined)
     */
    public static Document getWinerysPropertiesDefinitionXSDAsDocument(
            WinerysPropertiesDefinition wpd) {
        /*
         * This is a quick hack: an XML schema container is created for each element. Smarter
         * solution: create a hash from namespace to XML schema element and re-use that for each new
         * element Drawback of "smarter" solution: not a single XSD file any more
         */
        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder;
        try {
            docBuilder = docFactory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            ModelUtilities.logger.debug(e.getMessage(), e);
            throw new IllegalStateException("Could not instantiate document builder", e);
        }
        Document doc = docBuilder.newDocument();

        if (!ModelUtilities.allRequiredFieldsNonNull(wpd)) {
            // wpd not fully filled -> valid XSD cannot be provided
            // fallback: add comment and return "empty" document
            Comment comment =
                    doc.createComment("Required fields are missing in Winery's key/value properties definition.");
            doc.appendChild(comment);
            return doc;
        }

        // create XSD schema container
        Element schemaElement = doc.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "schema");
        doc.appendChild(schemaElement);
        schemaElement.setAttribute("elementFormDefault", "qualified");
        schemaElement.setAttribute("attributeFormDefault", "unqualified");
        schemaElement.setAttribute("targetNamespace", wpd.getNamespace());

        // create XSD element itself
        Element el = doc.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "element");
        schemaElement.appendChild(el);
        el.setAttribute("name", wpd.getElementName());
        Element el2 = doc.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "complexType");
        el.appendChild(el2);
        el = el2;
        el2 = doc.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "sequence");
        el.appendChild(el2);
        el = el2;

        // currently, "xsd" is a hardcoded prefix in the type definition
        el.setAttribute("xmlns:xsd", XMLConstants.W3C_XML_SCHEMA_NS_URI);

        for (PropertyDefinitionKV prop : wpd.getPropertyDefinitionKVList()) {
            el2 = doc.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "element");
            el.appendChild(el2);
            el2.setAttribute("name", prop.getKey());
            // prop.getType has the prefix included
            el2.setAttribute("type", prop.getType());
        }

        return doc;
    }

    /**
     * Removes an existing Winery's Properties definition. If no such definition exists, the
     * TEntityType is not modified
     */
    public static void removeWinerysPropertiesDefinition(TEntityType et) {
        for (Iterator<Object> iterator = et.getAny().iterator(); iterator.hasNext();) {
            Object o = iterator.next();
            if (o instanceof WinerysPropertiesDefinition) {
                iterator.remove();
                break;
            }
        }
    }

    public static void replaceWinerysPropertiesDefinition(TEntityType et,
            WinerysPropertiesDefinition wpd) {
        ModelUtilities.removeWinerysPropertiesDefinition(et);
        et.getAny().add(wpd);
    }

    public static String getBorderColor(TNodeType nt) {
        String borderColor = nt.getOtherAttributes().get(QNames.QNAME_BORDER_COLOR);
        if (borderColor == null) {
            borderColor = Util.getColor(nt.getName());
        }
        return borderColor;
    }

    public static String getColor(TRelationshipType rt) {
        String color = rt.getOtherAttributes().get(QNames.QNAME_COLOR);
        if (color == null) {
            color = Util.getColor(rt.getName());
        }
        return color;
    }

    /**
     * Returns the Properties. If no properties exist, the element is created
     * 
     * @return
     */
    public static org.eclipse.winery.model.tosca.TBoundaryDefinitions.Properties getProperties(
            TBoundaryDefinitions defs) {
        org.eclipse.winery.model.tosca.TBoundaryDefinitions.Properties properties =
                defs.getProperties();
        if (properties == null) {
            properties = new org.eclipse.winery.model.tosca.TBoundaryDefinitions.Properties();
            defs.setProperties(properties);
        }
        return properties;
    }

    /**
     * Special method to get the name of an extensible element as the TOSCA specification does not
     * have a separate super type for elements with a name
     * 
     * {@link org.eclipse.winery.common.Util.instanceSupportsNameAttribute(Class<? extends
     * TOSCAComponentId>)} is related
     * 
     * @param e the extensible element offering a name attribute (besides an id attribute)
     * @return the name of the extensible element
     * @throws IllegalStateException if e does not offer the method "getName"
     */
    public static String getName(TExtensibleElements e) {
        Method method;
        Object res;
        try {
            method = e.getClass().getMethod("getName");
            res = method.invoke(e);
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return (String) res;
    }

    /**
     * Returns the name of the given element. If the name does not exist or is empty, the id is
     * returned
     * 
     * {@see getName}
     * 
     * @return the name if there is a name field, if not, the id is returned. In case there is a
     *         Name field,
     */
    public static String getNameWithIdFallBack(TExtensibleElements ci) {
        Method method;
        String res = null;
        try {
            method = ci.getClass().getMethod("getName");
            res = (String) method.invoke(ci);
        } catch (Exception e) {}
        if (StringUtils.isEmpty(res)) {
            try {
                method = ci.getClass().getMethod("getId");
                res = (String) method.invoke(ci);
            } catch (Exception e2) {
                throw new IllegalStateException(e2);
            }
        }
        return res;
    }

    /**
     * Special method to set the name of an extensible element as the TOSCA specification does not
     * have a separate super type for elements with a name
     * 
     * @param e the extensible element offering a name attribute (besides an id attribute)
     * @param name the new name
     * @throws IllegalStateException if e does not offer the method "getName"
     */
    public static void setName(TExtensibleElements e, String name) {
        Method method;
        try {
            method = e.getClass().getMethod("setName", String.class);
            method.invoke(e, name);
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }

    public static boolean allRequiredFieldsNonNull(WinerysPropertiesDefinition wpd) {
        boolean valid = wpd.getNamespace() != null;
        valid = valid && (wpd.getElementName() != null);
        if (valid) {
            PropertyDefinitionKVList propertyDefinitionKVList = wpd.getPropertyDefinitionKVList();
            valid = (propertyDefinitionKVList != null);
            if (valid) {
                for (PropertyDefinitionKV def : propertyDefinitionKVList) {
                    valid = valid && (def.getKey() != null);
                    valid = valid && (def.getType() != null);
                }
            }
        }
        return valid;
    }

    /**
     * @return null if no explicit left is set
     */
    public static String getLeft(TNodeTemplate nodeTemplate) {
        Map<QName, String> otherAttributes = nodeTemplate.getOtherAttributes();
        String left =
                otherAttributes.get(new QName(Namespaces.TOSCA_WINERY_EXTENSIONS_NAMESPACE, "x"));
        return left;
    }

    /**
     * @return null if no explicit left is set
     */
    public static String getTop(TNodeTemplate nodeTemplate) {
        Map<QName, String> otherAttributes = nodeTemplate.getOtherAttributes();
        String top =
                otherAttributes.get(new QName(Namespaces.TOSCA_WINERY_EXTENSIONS_NAMESPACE, "y"));
        return top;
    }

    /**
     * locates targetObjectRef inside a topology template
     * 
     * @param topologyTemplate the topology template to search in
     * @param targetObjectRef the object ref as String
     * 
     * @return null if not found, otherwise the entity template in the topology
     */
    public static TEntityTemplate findNodeTemplateOrRequirementOfNodeTemplateOrCapabilityOfNodeTemplateOrRelationshipTemplate(
            TTopologyTemplate topologyTemplate, String targetObjectRef) {
        // We cannot use XMLs id pointing capabilities as we work on the Java model
        // Other option: modify the stored XML directly. This is more error prune than walking
        // through the whole topology
        for (TEntityTemplate t : topologyTemplate.getNodeTemplateOrRelationshipTemplate()) {
            if (t instanceof TNodeTemplate) {
                if (t.getId().equals(targetObjectRef)) {
                    return t;
                }
                TNodeTemplate nt = (TNodeTemplate) t;

                Requirements requirements = nt.getRequirements();
                if (requirements != null) {
                    for (TRequirement req : requirements.getRequirement()) {
                        if (req.getId().equals(targetObjectRef)) {
                            return req;
                        }
                    }
                }

                Capabilities capabilities = nt.getCapabilities();
                if (capabilities != null) {
                    for (TCapability cap : capabilities.getCapability()) {
                        if (cap.getId().equals(targetObjectRef)) {
                            return cap;
                        }
                    }
                }

            } else {
                assert (t instanceof TRelationshipTemplate);
                if (t.getId().equals(targetObjectRef)) {
                    return t;
                }
            }
        }

        // no return hit inside the loop: nothing was found
        return null;
    }

    /**
     * Returns the id of the given element
     * 
     * The TOSCA specification does NOT always put an id field. In the case of EntityTypes and
     * EntityTypeImplementations, there is no id, but a name field
     * 
     * This method abstracts from that fact.
     */
    public static String getId(TExtensibleElements ci) {
        Method method;
        Object res;
        try {
            method = ci.getClass().getMethod("getId");
            res = method.invoke(ci);
        } catch (Exception e) {
            // If no "getId" method is there, we try "getName"
            try {
                method = ci.getClass().getMethod("getName");
                res = method.invoke(ci);
            } catch (Exception e2) {
                throw new IllegalStateException(e2);
            }
        }
        return (String) res;
    }

    /**
     * Resolves a given id as requirement in the given ServiceTemplate
     * 
     * @return null if not found
     */
    public static TRequirement resolveRequirement(TServiceTemplate serviceTemplate, String reference) {
        TRequirement resolved = null;
        for (TEntityTemplate tmpl : serviceTemplate.getTopologyTemplate()
                .getNodeTemplateOrRelationshipTemplate()) {
            if (tmpl instanceof TNodeTemplate) {
                TNodeTemplate n = (TNodeTemplate) tmpl;
                Requirements requirements = n.getRequirements();
                if (requirements != null) {
                    for (TRequirement req : n.getRequirements().getRequirement()) {
                        if (req.getId().equals(reference)) {
                            resolved = req;
                            resolved.getOtherAttributes().put(new QName("refNodeId"), n.getId());
                            resolved.getOtherAttributes()
                                    .put(new QName("refNodeName"), n.getName());
                            break;
                        }
                    }
                }
            }
        }
        return resolved;
    }

    public static TCapability resolveCapability(TServiceTemplate serviceTemplate, String reference) {
        TCapability resolved = null;
        for (TEntityTemplate tmpl : serviceTemplate.getTopologyTemplate()
                .getNodeTemplateOrRelationshipTemplate()) {
            if (tmpl instanceof TNodeTemplate) {
                TNodeTemplate n = (TNodeTemplate) tmpl;
                Capabilities capabilities = n.getCapabilities();
                if (capabilities != null) {
                    for (TCapability cap : n.getCapabilities().getCapability()) {
                        if (cap.getId().equals(reference)) {
                            resolved = cap;
                            resolved.getOtherAttributes().put(new QName("refNodeId"), n.getId());
                            resolved.getOtherAttributes()
                                    .put(new QName("refNodeName"), n.getName());
                            break;
                        }
                    }
                }
            }
        }
        return resolved;
    }

    public static TNodeTemplate resolveNodeTemplate(TServiceTemplate serviceTemplate,
            String reference) {
        TNodeTemplate resolved = null;
        for (TEntityTemplate tmpl : serviceTemplate.getTopologyTemplate()
                .getNodeTemplateOrRelationshipTemplate()) {
            if (tmpl instanceof TNodeTemplate) {
                TNodeTemplate n = (TNodeTemplate) tmpl;
                if (n.getId().equals(reference)) {
                    resolved = n;
                }
            }
        }
        return resolved;
    }

    public static TRelationshipTemplate resolveRelationshipTemplate(
            TServiceTemplate serviceTemplate, String reference) {
        TRelationshipTemplate resolved = null;
        for (TEntityTemplate tmpl : serviceTemplate.getTopologyTemplate()
                .getNodeTemplateOrRelationshipTemplate()) {
            if (tmpl instanceof TRelationshipTemplate) {
                TRelationshipTemplate n = (TRelationshipTemplate) tmpl;
                if (n.getId().equals(reference)) {
                    resolved = n;
                }
            }
        }
        return resolved;
    }

    public static TPlan resolvePlan(TServiceTemplate serviceTemplate, String reference) {
        TPlan resolved = null;
        TPlans plans = serviceTemplate.getPlans();
        if (plans == null) {
            return null;
        }
        for (TPlan p : plans.getPlan()) {
            if (p.getId().equals(reference)) {
                resolved = p;
            }
        }
        return resolved;
    }

    /**
     * Sets the x coordinate of a {@link TNodeTemplate}.
     *
     * @param nodeTemplate the nodeTemplate to be altered
     * @param coordinate the value of the coordinate to be set
     * @return the altered {@link TNodeTemplate}
     */
    public static TNodeTemplate setLeft(TNodeTemplate nodeTemplate, String coordinate) {

        Map<QName, String> otherNodeTemplateAttributes = nodeTemplate.getOtherAttributes();
        otherNodeTemplateAttributes.put(
                new QName(Namespaces.TOSCA_WINERY_EXTENSIONS_NAMESPACE, "x"), coordinate);

        return nodeTemplate;
    }

    /**
     * Sets the y coordinate of a {@link TNodeTemplate}.
     *
     * @param nodeTemplate the nodeTemplate to be altered
     * @param coordinate the value of the coordinate to be set
     * @return the altered {@link TNodeTemplate}
     */
    public static TNodeTemplate setTop(TNodeTemplate nodeTemplate, String coordinate) {

        Map<QName, String> otherNodeTemplateAttributes = nodeTemplate.getOtherAttributes();
        otherNodeTemplateAttributes.put(
                new QName(Namespaces.TOSCA_WINERY_EXTENSIONS_NAMESPACE, "y"), coordinate);

        return nodeTemplate;

    }

    /**
     * This method instantiates a {@link TNodeTemplate} for a given {@link TNodeType}.
     *
     * @param nodeType the {@link TNodeType} used for the {@link TNodeTemplate} instantiation.
     *
     * @return the instantiated {@link TNodeTemplate}
     */
    public static TNodeTemplate instantiateNodeTemplate(TNodeType nodeType) {

        TNodeTemplate nodeTemplate = new TNodeTemplate();

        nodeTemplate.setId(UUID.randomUUID().toString());
        nodeTemplate.setName(nodeType.getName());
        nodeTemplate.setType(new QName(nodeType.getTargetNamespace(), nodeType.getName()));

        // add capabilities to the NodeTemplate
        if (nodeType.getCapabilityDefinitions() != null) {
            for (TCapabilityDefinition cd : nodeType.getCapabilityDefinitions()
                    .getCapabilityDefinition()) {
                TCapability capa = new TCapability();
                capa.setId(UUID.randomUUID().toString());
                capa.setName(cd.getCapabilityType().getLocalPart());
                capa.setType(new QName(cd.getCapabilityType().getNamespaceURI(), cd
                        .getCapabilityType().getLocalPart()));
                nodeTemplate.setCapabilities(new Capabilities());
                nodeTemplate.getCapabilities().getCapability().add(capa);
            }
        }

        // add requirements
        if (nodeType.getRequirementDefinitions() != null
                && nodeType.getRequirementDefinitions().getRequirementDefinition() != null) {
            Requirements requirementsNode = new Requirements();
            nodeTemplate.setRequirements(requirementsNode);
            for (TRequirementDefinition definition : nodeType.getRequirementDefinitions()
                    .getRequirementDefinition()) {
                TRequirement newRequirement = new TRequirement();
                newRequirement.setName(definition.getName());
                newRequirement.setId(definition.getName());
                newRequirement.setType(definition.getRequirementType());
                nodeTemplate.getRequirements().getRequirement().add(newRequirement);
            }
        }

        return nodeTemplate;
    }

    /**
     * This method instantiates a {@link TRelationshipTemplate} for a given
     * {@link TRelationshipType}.
     *
     * @param nodeType the {@link TRelationshipType} used for the {@link TRelationshipTemplate}
     *        instantiation.
     * @param sourceNodeTemplate the source {@link TNodeTemplate} of the connection
     * @param targetNodeTemplate the target {@link TNodeTemplate} of the connection
     *
     * @return the instantiated {@link TRelationshipTemplate}
     */
    public static TRelationshipTemplate instantiateRelationshipTemplate(
            TRelationshipType relationshipType, TNodeTemplate sourceNodeTemplate,
            TNodeTemplate targetNodeTemplate) {

        TRelationshipTemplate relationshipTemplate = new TRelationshipTemplate();
        relationshipTemplate.setId(UUID.randomUUID().toString());
        relationshipTemplate.setName(relationshipType.getName());
        relationshipTemplate.setType(new QName(relationshipType.getTargetNamespace(),
                relationshipType.getName()));

        // connect the NodeTemplates
        SourceElement source = new SourceElement();
        source.setRef(sourceNodeTemplate);
        relationshipTemplate.setSourceElement(source);
        TargetElement target = new TargetElement();
        target.setRef(targetNodeTemplate);
        relationshipTemplate.setTargetElement(target);

        return relationshipTemplate;
    }

    public static List<TRequirement> instantiateRequirements(TNodeType nodeType) {
        if (nodeType.getRequirementDefinitions() == null) {
            return null;
        }

        List<TRequirementDefinition> requirementDefinition =
                nodeType.getRequirementDefinitions().getRequirementDefinition();
        if (null == requirementDefinition || requirementDefinition.isEmpty()) {
            return null;
        }

        List<TRequirement> result = new ArrayList<TRequirement>();
        for (TRequirementDefinition definition : requirementDefinition) {
            TRequirement newRequirement = new TRequirement();
            newRequirement.setName(definition.getName());
            newRequirement.setId(UUID.randomUUID().toString());
            newRequirement.setType(definition.getRequirementType());
            result.add(newRequirement);
        }
        return result;
    }

    public static List<TCapability> instantiateCapabilities(TNodeType nodeType) {
        if (nodeType.getCapabilityDefinitions() == null) {
            return null;
        }

        List<TCapabilityDefinition> capabilityDefinition =
                nodeType.getCapabilityDefinitions().getCapabilityDefinition();
        if (null == capabilityDefinition || capabilityDefinition.isEmpty()) {
            return null;
        }

        List<TCapability> result = new ArrayList<TCapability>();
        for (TCapabilityDefinition definition : capabilityDefinition) {
            TCapability newCapa = new TCapability();
            newCapa.setName(definition.getName());
            newCapa.setId(UUID.randomUUID().toString());
            newCapa.setType(definition.getCapabilityType());
            result.add(newCapa);
        }
        return result;
    }
    
    public static Element buildPropertiesElement(Map<String, String> properties, String propNS) {
        Document document = null;
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            document = builder.newDocument();
        } catch (ParserConfigurationException e) {
            System.out.println(e.getMessage());
        }

        Element root = document.createElement("Properties");
        root.setAttribute("xmlns", propNS);
        for (Entry<String, String> entry : properties.entrySet()) {
            if (null != entry.getKey() && null != entry.getValue()) {
                Element ele = document.createElementNS(propNS, entry.getKey());
                ele.setTextContent(entry.getValue());
                root.appendChild(ele);
            }
        }

        return root;
    }
    
    public static Map<String, String> resolvePropertiesElement(Element propRootElement) {
        Map<String, String> map = new HashMap<String, String>();
        NodeList childList = propRootElement.getChildNodes();
        for (int i = 0; i < childList.getLength(); i++) {
            Node child = childList.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                String propertyName = child.getLocalName();
                String propertyValue = child.getTextContent();
                map.put(propertyName, propertyValue);
            }
        }
        return map;
    }
}
